package com.tisumoon.thirdapi;

import java.util.concurrent.atomic.AtomicBoolean;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.Intent;

import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.location.LocationClientOption.LocationMode;
import com.wei.c.L;
import com.wei.c.data.abs.AbsJson;
import com.wei.c.phone.Network;

/**
 * <pre>
 * 网络定位：android.permission.ACCESS_COARSE_LOCATION
 * 访问GPS定位：android.permission.ACCESS_FINE_LOCATION
 * 访问wifi网络信息，wifi信息会用于进行网络定位：android.permission.ACCESS_WIFI_STATE
 * 获取运营商信息，用于支持提供运营商信息相关的接口：android.permission.ACCESS_NETWORK_STATE
 * 获取wifi的获取权限，wifi信息会用来进行网络定位：android.permission.CHANGE_WIFI_STATE
 * 读取手机当前的状态：android.permission.READ_PHONE_STATE
 * 写入扩展存储，向扩展卡写入数据，用于写入离线定位数据：android.permission.WRITE_EXTERNAL_STORAGE
 * 访问网络，网络定位需要上网：android.permission.INTERNET
 * SD卡读取权限，用户写入离线定位数据：android.permission.MOUNT_UNMOUNT_FILESYSTEMS
 * 允许应用读取低级别的系统日志文件：android.permission.READ_LOGS
 * </pre>
 * 
 * @author Wei.Chou
 *
 */
public class Baidu {

	/**定位失败，通常由于启用了飞行模式而且wifi关闭（这种情况下Gps开启也没用）**/
	public static final String ACTION_LOC_FAILED_CLIENT			= Baidu.class.getName() + ".intent.action.LOC_FAILED_CLIENT";
	/**服务端定位失败**/
	public static final String ACTION_LOC_FAILED_SERVER			= Baidu.class.getName() + ".intent.action.LOC_FAILED_SERVER";
	/**key参数异常**/
	public static final String ACTION_LOC_FAILED_KEY_ERROR		= Baidu.class.getName() + ".intent.action.LOC_FAILED_KEY_ERROR";
	/**key验证失败**/
	public static final String ACTION_LOC_FAILED_KEY_AUTHORIZE	= Baidu.class.getName() + ".intent.action.LOC_FAILED_KEY_AUTHORIZE";
	/**未知信息**/
	public static final String ACTION_LOC_UNKNOWN					= Baidu.class.getName() + ".intent.action.LOC_UNKNOWN";
	/**定位成功**/
	public static final String ACTION_LOC_SUCCESS					= Baidu.class.getName() + ".intent.action.LOC_SUCCESS";

	public static final String EXTRA_CODE							= Baidu.class.getName() + ".intent.EXTRA_CODE";
	public static final String EXTRA_RESULT						= Baidu.class.getName() + ".intent.EXTRA_RESULT";

	/**gcj02国测局经纬度坐标系**/
	public static final String COOR_TYPE_GCJ02					= "gcj02";
	/**bd09百度墨卡托坐标系**/
	public static final String COOR_TYPE_BD09						= "bd09";
	/**bd09ll百度经纬度坐标系**/
	public static final String COOR_TYPE_BD09LL					= "bd09ll";

	private static final int sTimeScanSep						= 1000;
	private static final int TIME_SCAN_5							= 1000 * 5;

	private static LocationClient mLocClient;
	private static Context mAppContext;
	private static AtomicBoolean mOnce				= new AtomicBoolean(true);

	private static boolean isOneTimeReq(int timeScan) {
		return timeScan < sTimeScanSep;
	}

	/**定时定位**/
	public static int requestLocationTimerScan(Context context) {
		return requestLocationTimerScan(context, true, true, TIME_SCAN_5);
	}

	/**
	 * 定时定位
	 * <pre>
	 * 当所设的整数值大于等于1000（ms）时，定位SDK内部使用定时定位模式。调用requestLocation()后，每隔设定的时间，定位SDK就会进行一次定位。
	 * 如果定位SDK根据定位依据发现位置没有发生变化，就不会发起网络请求，返回上一次定位的结果；
	 * 如果发现位置改变，就进行网络请求进行定位，得到新的定位结果。定时定位时，调用一次requestLocation()，会定时监听到定位结果。
	 * 当不设此项，或者所设的整数值小于1000（ms）时，采用一次定位模式。每调用一次requestLocation()，定位SDK会发起一次定位。请求定位与监听结果一一对应。
	 * 设定了定时定位后，可以热切换成一次定位，需要重新设置时间间隔小于1000（ms）即可。
	 * locationClient对象stop后，将不再进行定位。如果设定了定时定位模式后，多次调用requestLocation()，则是每隔一段时间进行一次定位，
	 * 同时额外的定位请求也会进行定位，但频率不会超过1秒一次。
	 * </pre>
	 * @return
	 * 0：正常发起了定位；
	 * 1：服务没有启动；
	 * 2：没有监听函数；
	 * 6：请求间隔过短。 前后两次请求定位时间间隔不能小于1000ms（这里指调用{@link #requestXxx()}方法的时间间隔）。
	 */
	public static int requestLocationTimerScan(Context context, boolean needAddress, boolean needDeviceDirect, int timeScan) {
		ensureInitLocationClient(context, needAddress, needDeviceDirect, timeScan);
		return mLocClient.requestLocation();
	}

	/**一次定位**/
	public static int requestLocation(Context context) {
		return requestLocation(context, true, true);
	}

	/**
	 * 一次定位
	 * @param context
	 * @param needAddress
	 * @param needDeviceDirect
	 * @return
	 * 0：正常发起了定位；
	 * 1：服务没有启动；
	 * 2：没有监听函数；
	 * 6：请求间隔过短。 前后两次请求定位时间间隔不能小于1000ms（这里指调用{@link #requestXxx()}方法的时间间隔）。
	 */
	public static int requestLocation(Context context, boolean needAddress, boolean needDeviceDirect) {
		ensureInitLocationClient(context, needAddress, needDeviceDirect, 1);
		return mLocClient.requestLocation();
	}

	/**
	 * 离线定位。无法获取手机方向和地址信息。飞行模式下，即使开启Gps也无法进行离线定位。
	 * @param context
	 * @return
	 * 0：正常发起了定位；
	 * 1：服务没有启动；
	 * 2：没有监听函数。
	 */
	public static int requestOfflineLocation(Context context) {
		ensureInitLocationClient(context, true, true, 1);
		return mLocClient.requestOfflineLocation();
	}

	private static void ensureInitLocationClient(Context context, boolean needAddress, boolean needDeviceDirect, int timeScan) {
		if (mLocClient == null) {
			mAppContext = context.getApplicationContext();
			mLocClient = new LocationClient(mAppContext);

			LocationClientOption option = new LocationClientOption();
			option.setCoorType(COOR_TYPE_BD09LL);// 返回定位结果的坐标系(gcj02国测局经纬度坐标系, bd09百度墨卡托坐标系, bd09ll百度经纬度坐标系)
			option.setIsNeedAddress(needAddress);
			option.setLocationMode(LocationMode.Hight_Accuracy);	//定位模式 -- 高精度-- 低耗 -- 设备--
			option.setOpenGps(true); //打开GPS
			//option.setLocationNotify(arg0);
			option.setNeedDeviceDirect(needDeviceDirect);	//返回的定位结果包含手机机头的方向(在网络定位中，获取手机机头所指的方向)
			option.setProdName(context.getApplicationInfo().packageName);
			/*
			 * 当所设的整数值大于等于1000（ms）时，定位SDK内部使用定时定位模式。调用requestLocation()后，每隔设定的时间，定位SDK就会进行一次定位。
			 * 如果定位SDK根据定位依据发现位置没有发生变化，就不会发起网络请求，返回上一次定位的结果；
			 * 如果发现位置改变，就进行网络请求进行定位，得到新的定位结果。定时定位时，调用一次requestLocation()，会定时监听到定位结果。
			 * 当不设此项，或者所设的整数值小于1000（ms）时，采用一次定位模式。每调用一次requestLocation()，定位SDK会发起一次定位。请求定位与监听结果一一对应。
			 * 设定了定时定位后，可以热切换成一次定位，需要重新设置时间间隔小于1000（ms）即可。
			 * locationClient对象stop后，将不再进行定位。如果设定了定时定位模式后，多次调用requestLocation()，则是每隔一段时间进行一次定位，
			 * 同时额外的定位请求也会进行定位，但频率不会超过1秒一次。
			 */
			option.setScanSpan(timeScan);	//1000 * 60 * 1	//定时定位的时间间隔。单位ms
			//option.setTimeOut(3000);
			mLocClient.setLocOption(option);
		} else {
			if (mOnce.get() && !isOneTimeReq(timeScan)) {
				mOnce.set(false);
				LocationClientOption option = mLocClient.getLocOption();
				option.setScanSpan(timeScan);
				mLocClient.setLocOption(option);
			} else if (!mOnce.get() && isOneTimeReq(timeScan)) {
				mOnce.set(true);
				LocationClientOption option = mLocClient.getLocOption();
				option.setScanSpan(timeScan);
				mLocClient.setLocOption(option);
			}
		}
		if (!mLocClient.isStarted()) {
			mLocClient.registerLocationListener(mBDLocListener);
			mLocClient.start();
		}
	}

	public static boolean isOnce() {
		return mOnce.get();
	}

	public static void stop() {
		if (mLocClient != null) {
			mLocClient.unRegisterLocationListener(mBDLocListener);
			mLocClient.stop();
		}
	}

	public static void destroy() {
		stop();
		mLocClient = null;
	}

	/**
	 * 文档描述：
	 * <pre>
	 * 离线定位功能：用户请求过得基站定位结果会缓存在本地文件。离线定位结果为缓存结果，精度低于在线的定位结果。离线定位结果没有地址信息。
	 * 只有使用网络定位的情况下，才能获取当前位置的反地理编码描述（地址信息）。
	 * 高精度定位模式：这种定位模式下，会同时使用网络定位和GPS定位，优先返回最高精度的定位结果（随机返回网络结果或者Gps结果）。
	 * 
	 * <pre>
	 * 61 ： GPS定位结果
	 * 62 ： 扫描整合定位依据失败。此时定位结果无效
	 * 63 ： 网络异常，没有成功向服务器发起请求。此时定位结果无效
	 * 65 ： 定位缓存的结果
	 * 66 ： 离线定位结果。通过requestOfflineLocaiton调用时对应的返回结果
	 * 67 ： 离线定位失败。通过requestOfflineLocaiton调用时对应的返回结果
	 * 68 ： 网络连接失败时，查找本地离线定位时对应的返回结果
	 * 161： 表示网络定位结果
	 * 162~167： 服务端定位失败
	 * 502：key参数错误
	 * 505：key不存在或者非法
	 * 601：key服务被开发者自己禁用
	 * 602：key mcode不匹配
	 * 501～700：key验证失败
	 * 如果不能定位，请记住这个返回值，并到我们的hi群或者贴吧中交流。若返回值是162~167，请发送邮件至mapapi@baidu.com反馈。
	 * </pre>
	 */
	private static BDLocationListener mBDLocListener = new BDLocationListener() {

		@Override
		public void onReceiveLocation(final BDLocation loc) {
			int locType = loc.getLocType();
			switch (locType) {
			case BDLocation.TypeCriteriaException:	//62
			case BDLocation.TypeNetWorkException:	//63
			case BDLocation.TypeOffLineLocationFail:	//67
				//定位失败，通常由于启用了飞行模式而且wifi关闭
				Intent intent = new Intent(ACTION_LOC_FAILED_CLIENT);
				intent.putExtra(EXTRA_CODE, locType);
				mAppContext.sendBroadcast(intent);
				L.e(Baidu.class, "定位失败");
				destroy();
				break;
			case 162:
			case 163:
			case 164:
			case 165:
			case 166:
			case BDLocation.TypeServerError:	//167
				//服务端定位失败
				intent = new Intent(ACTION_LOC_FAILED_SERVER);
				intent.putExtra(EXTRA_CODE, locType);
				mAppContext.sendBroadcast(intent);
				L.e(Baidu.class, "服务端定位失败");
				destroy();
				break;
			case 502:
			case 505:
			case 601:
			case 602:
				//key参数异常
				intent = new Intent(ACTION_LOC_FAILED_KEY_ERROR);
				intent.putExtra(EXTRA_CODE, locType);
				mAppContext.sendBroadcast(intent);
				L.e(Baidu.class, "key参数异常");
				destroy();
				break;
			case BDLocation.TypeNone:
			default:
				if (locType >= 501 && locType <= 700) {	//key验证失败
					intent = new Intent(ACTION_LOC_FAILED_KEY_AUTHORIZE);
					intent.putExtra(EXTRA_CODE, locType);
					mAppContext.sendBroadcast(intent);
					L.e(Baidu.class, "key验证失败");
				} else {
					intent = new Intent(ACTION_LOC_UNKNOWN);
					intent.putExtra(EXTRA_CODE, locType);
					mAppContext.sendBroadcast(intent);
				}
				destroy();
				break;
			case BDLocation.TypeGpsLocation:
			case BDLocation.TypeNetWorkLocation:
			case BDLocation.TypeCacheLocation:
			case BDLocation.TypeOffLineLocation:
			case BDLocation.TypeOffLineLocationNetworkFail:	//68在线失败，使用离线结果，可用
				LocData locData;
				if (locType == BDLocation.TypeGpsLocation) {
					LocGps locGps = new LocGps();
					locData = locGps;
					locGps.direction = loc.getDirection();
					locGps.speed = loc.getSpeed();
					locGps.satelliteNumber = loc.getSatelliteNumber();
				} else if (locType == BDLocation.TypeNetWorkLocation) {
					LocNet locNet;
					if (Network.isAirplaneModeOn(mAppContext)) {	//飞行模式，没有运营商信息
						locNet = new LocNet();
					} else {
						LocNetvGsm locNetvGsm = new LocNetvGsm();
						locNet = locNetvGsm;
						int operators = loc.getOperators();
						locNetvGsm.operators = operators;
						locNetvGsm.operatorName = parseOperators(operators);
					}
					locData = locNet;
					locNet.direction = loc.getDirection();
					locNet.province = loc.getProvince();
					locNet.city = loc.getCity();
					locNet.cityCode = loc.getCityCode();
					locNet.district = loc.getDistrict();
					locNet.street = loc.getStreet();
					locNet.streetNumber = loc.getStreetNumber();
					locNet.addrStr = loc.getAddrStr();
					locNet.floor = loc.getFloor();
					//locNet.adUrl = loc.getAdUrl(ak);
					locNet.networkLocationType = loc.getNetworkLocationType();
				} else {
					locData = new LocData();
				}
				locData.time = loc.getTime();
				locData.locType = loc.getLocType();
				locData.longitude = loc.getLongitude();
				locData.latitude = loc.getLatitude();
				locData.altitude = loc.getAltitude();
				locData.radius = loc.getRadius();

				intent = new Intent(ACTION_LOC_SUCCESS);
				intent.putExtra(EXTRA_CODE, locType);
				intent.putExtra(EXTRA_RESULT, locData.toJson());
				mAppContext.sendBroadcast(intent);
				if (mOnce.get()) stop();
				break;
			}
		}
	};

	/**解析运营商信息**/
	private static String parseOperators(int operators) {
		switch (operators) {
		case BDLocation.OPERATORS_TYPE_MOBILE:
			return "中国移动";
		case BDLocation.OPERATORS_TYPE_UNICOM:
			return "中国联通";
		case BDLocation.OPERATORS_TYPE_TELECOMU:
			return "中国电信";
		case BDLocation.OPERATORS_TYPE_UNKONW:
		default:
			return "未知运营商";
		}
	}

	/*
	public static void saveCoordinate(Context context, double longitude, double latitude, double altitude) {
		SPref.edit(context, Baidu.class)
		.putString("longitude", String.valueOf(longitude))
		.putString("latitude", String.valueOf(latitude))
		.putString("altitude", String.valueOf(altitude))
		.commit();
	}

	public static boolean isCoordinateSameAsPrev(Context context, double longitude, double latitude, double altitude) {
		double[] coordinate = getCoordinate(context);
		boolean b = coordinate[0] == longitude && coordinate[1] == latitude && coordinate[2] == altitude;
		L.d(Baidu.class, "isCoordinateSameAsPrev:" + b);
		return b;
	}

	public static double[] getCoordinate(Context context) {
		SharedPreferences sPref = SPref.getSPref(context, Baidu.class);
		return new double[] {
				Double.valueOf(sPref.getString("longitude", "0")),
				Double.valueOf(sPref.getString("latitude", "0")),
				Double.valueOf(sPref.getString("altitude", "0"))};
	}*/

	/**基本定位信息，或离线定位（飞行模式下，即使开启Gps也无法进行离线定位），没有手机方向**/
	public static class LocData extends AbsJson<LocData> {
		/**e.g: 2014-8-29 20:14:36**/
		public String time;
		/**定位类型：Gps、网络、离线、缓存**/
		public int locType;
		/**经度**/
		public double longitude;
		/**纬度**/
		public double latitude;
		/**高度信息，目前没有实现（可能是当前位置的海拔高度）**/
		public double altitude;
		/**定位精度，单位：米**/
		public float radius;

		@Override
		public LocData fromJson(String json) {
			return fromJsonWithAllFields(json, LocData.class);
		}

		@Override
		public String toJson() {
			return toJsonWithAllFields(this);
		}

		@Override
		public boolean isBelongToMe(JSONObject json) {
			try {
				return json.has("type") && json.getString("type").equals(type);
			} catch (JSONException e) {
				e.printStackTrace();
				return false;
			}
		}

		protected String type = LocData.class.getName();
	}

	/**Gps定位结果，没有地址信息**/
	public static class LocGps extends LocData {
		/**手机方向**/
		public float direction;
		/**移动速度**/
		public float speed;
		/**卫星编号**/
		public int satelliteNumber;

		@Override
		public LocGps fromJson(String json) {
			return fromJsonWithAllFields(json, LocGps.class);
		}

		public LocGps() {
			type = LocGps.class.getName();
		}
	}

	/**网络定位结果，有地址和手机方向。有任何网络连接，包括飞行模式但wifi开启的情况**/
	public static class LocNet extends LocData {
		/**手机方向**/
		public float direction;
		/**省份**/
		public String province;
		/**城市**/
		public String city;
		public String cityCode;
		/**区县**/
		public String district;

		public String street;

		public String streetNumber;
		/**地址**/
		public String addrStr;
		/**参数：ak - 此ak为开发者参与位置营销联盟的ak<br/>返回：开发者的位置营销url<br/>loc.getAdUrl(ak);*/
		public String adUrl;
		/**获取楼层信息,仅室内定位时有效**/
		public String floor;
		/**在网络定位结果的情况下，获取网络定位结果是通过基站定位得到的还是通过wifi定位得到的**/
		public String networkLocationType;

		@Override
		public LocNet fromJson(String json) {
			return fromJsonWithAllFields(json, LocNet.class);
		}

		public LocNet() {
			type = LocNet.class.getName();
		}
	}

	/**有网络连接同时有蜂窝网络，才有运营商信息**/
	public static class LocNetvGsm extends LocNet {
		public int operators;
		public String operatorName;

		@Override
		public LocNetvGsm fromJson(String json) {
			return fromJsonWithAllFields(json, LocNetvGsm.class);
		}

		public LocNetvGsm() {
			type = LocNetvGsm.class.getName();
		}
	}
}
